﻿using System.Runtime.InteropServices;
using MathS = System.Math;

#if F32
	using Num = System.Single;
	namespace Reign.Core.MathF32
#endif
#if F64
	using Num = System.Double;
	namespace Reign.Core.MathF64
#endif
#if I32
	using Num = System.Int32;
	namespace Reign.Core.MathI32
#endif
{
	[StructLayout(LayoutKind.Sequential)]
	public struct Vector2
	{
		#region Properties
		public Num X, Y;
		public const int SizeOf = sizeof(Num) * 2;

		public Num R
		{
			get {return X;}
			set {X = value;}
		}

		public Num G
		{
			get {return Y;}
			set {Y = value;}
		}

		public Vector2 XY
		{
			get {return new Vector2(X, Y);}
			set
			{
				X = value.X;
				Y = value.Y;
			}
		}

		public Vector2 YX
		{
			get {return new Vector2(Y, X);}
			set
			{
				Y = value.X;
				X = value.Y;
			}
		}
		#endregion

		#region Constructors
		public Vector2(Num value)
		{
			X = value;
			Y = value;
		}

		public Vector2(Num x, Num y)
		{
			X = x;
			Y = y;
		}
		#endregion

		#region Operators
		// +
		public static Vector2 operator+(Vector2 p1, Vector2 p2) {return new Vector2(p1.X+p2.X, p1.Y+p2.Y);}
		public static Vector2 operator+(Vector2 p1, Num p2) {return new Vector2(p1.X+p2, p1.Y+p2);}
		public static Vector2 operator+(Num p1, Vector2 p2) {return new Vector2(p1+p2.X, p1+p2.Y);}
		// -
		public static Vector2 operator-(Vector2 p1, Vector2 p2) {return new Vector2(p1.X-p2.X, p1.Y-p2.Y);}
		public static Vector2 operator-(Vector2 p1, Num p2) {return new Vector2(p1.X-p2, p1.Y-p2);}
		public static Vector2 operator-(Num p1, Vector2 p2) {return new Vector2(p1-p2.X, p1-p2.Y);}
		public static Vector2 operator-(Vector2 p1) {return new Vector2(-p1.X, -p1.Y);}
		// *
		public static Vector2 operator*(Vector2 p1, Vector2 p2) {return new Vector2(p1.X*p2.X, p1.Y*p2.Y);}
		public static Vector2 operator*(Vector2 p1, Num p2) {return new Vector2(p1.X*p2, p1.Y*p2);}
		public static Vector2 operator*(Num p1, Vector2 p2) {return new Vector2(p1*p2.X, p1*p2.Y);}
		// /
		public static Vector2 operator/(Vector2 p1, Vector2 p2) {return new Vector2(p1.X/p2.X, p1.Y/p2.Y);}
		public static Vector2 operator/(Vector2 p1, Num p2) {return new Vector2(p1.X/p2, p1.Y/p2);}
		public static Vector2 operator/(Num p1, Vector2 p2) {return new Vector2(p1/p2.X, p1/p2.Y);}
		// ==
		public static bool operator==(Vector2 p1, Vector2 p2) {return (p1.X==p2.X && p1.Y==p2.Y);}
		public static bool operator!=(Vector2 p1, Vector2 p2) {return (p1.X!=p2.X || p1.Y!=p2.Y);}
		#endregion

		#region Methods
		public override bool Equals(object obj)
		{
			return obj != null && (Vector2)obj == this;
		}

		public override string ToString()
		{
			return string.Format("{0}, {1}", X, Y);
		}

		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		public Vector2 Abs()
		{
			return new Vector2(MathS.Abs(X), MathS.Abs(Y));
		}

		public Vector2 Pow(Num value)
		{
			return new Vector2((Num)MathS.Pow(X, value), (Num)MathS.Pow(Y, value));
		}

		public Vector2 Floor()
		{
			#if I32
			return this;
			#else
			return new Vector2((Num)MathS.Floor(X), (Num)MathS.Floor(Y));
			#endif
		}

		public Vector2 Max(Num value)
		{
			return new Vector2(MathS.Max(X, value), MathS.Max(Y, value));
		}

		public Vector2 Min(Num value)
		{
			return new Vector2(MathS.Min(X, value), MathS.Min(Y, value));
		}

		public Vector2 Cross()
		{
			return new Vector2(-Y, X);
		}

		public Vector2 Normalize()
		{
			return this * (Math.One / (Num)MathS.Sqrt((X*X) + (Y*Y)));
		}

		public Vector2 Normalize(out Num distance)
		{
			Num dis = (Num)MathS.Sqrt((X*X) + (Y*Y));
			distance = dis;
			return this * (Math.One/dis);
		}
		public Vector2 NormalizeSafe()
		{
			Num dis = (Num)MathS.Sqrt((X*X) + (Y*Y));
			if (dis == 0) return new Vector2();
			return this * (Math.One/dis);
		}
		public Vector2 NormalizeSafe(out Num distance)
		{
			Num dis = (Num)MathS.Sqrt((X*X) + (Y*Y));
			distance = dis;
			if (dis == 0) return new Vector2();
			return this * (Math.One/dis);
		}

		public Num Dot()
		{
			return (X*X) + (Y*Y);
		}

		public Num Dot(Vector2 vector)
		{
			return (X*vector.X) + (Y*vector.Y);
		}

		public Num Distance()
		{
			return (Num)MathS.Sqrt((X*X) + (Y*Y));
		}

		public Num Angle()
		{
			Vector2 vec = this.Normalize();
			Num val = vec.X;
			val = (val > 1) ? 1 : val;
			val = (val < -1) ? -1 : val;
			return (Num)MathS.Acos(val);
		}

		public Num Angle(Vector2 vector)
		{
			Vector2 vec = this.Normalize();
			Num val = vec.Dot(vector.Normalize());
			val = (val > 1) ? 1 : val;
			val = (val < -1) ? -1 : val;
			return (Num)MathS.Acos(val);
		}

		public Num Angle90()
		{
			Vector2 vec = this.Normalize();
			Num val = MathS.Abs(vec.X);
			val = (val > 1) ? 1 : val;
			return (Num)MathS.Acos(val);
		}

		Num Angle90(Vector2 vector)
		{
			Vector2 vec = this.Normalize();
			Num val = MathS.Abs(vec.Dot(vector.Normalize()));
			val = (val > 1) ? 1 : val;
			return (Num)MathS.Acos(val);
		}

		public Num Angle180()
		{
			Vector2 vec = this.Normalize();
			return ((Num)MathS.Atan2(-vec.Y, vec.X)) % Math.Pi2;
		}

		public Num Angle180(Vector2 vector)
		{
		    Vector2 vec = this.Normalize();
		    vector = vector.Normalize();
		    return ((Num)MathS.Atan2((vec.X*vector.Y)-(vec.Y*vector.X), (vec.X*vector.X)+(vec.Y*vector.Y))) % Math.Pi2;
		}

		public Num Angle360()
		{
		    Vector2 vec = this.Normalize();
		    Num value = ((Num)MathS.Atan2(-vec.Y, vec.X)) % Math.Pi2;
		    return (value < 0) ? ((Math.Pi+value)+Math.Pi) : value;
		}

		public Num Angle360(Vector2 vector)
		{
		    Vector2 vec = this.Normalize();
		    vector = vector.Normalize();
		    Num value = ((Num)MathS.Atan2((vec.X*vector.Y)-(vec.Y*vector.X), (vec.X*vector.X)+(vec.Y*vector.Y))) % Math.Pi2;
		    return (value < 0) ? ((Math.Pi+value)+Math.Pi) : value;
		}
		#endregion
	}
}
